Completed
Push — master ( 2e96ee...51b9e4 )
by Rain
01:37
created

jquery.vide.js ➔ Vide   C

Complexity

Conditions 8
Paths 18

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 18
dl 0
loc 31
rs 5.3846
c 0
b 0
f 0
nop 3
1
/*
2
 *  Vide - v0.3.0
3
 *  Easy as hell jQuery plugin for video backgrounds.
4
 *  http://vodkabears.github.io/vide/
5
 *
6
 *  Made by Ilya Makarov
7
 *  Under MIT License
8
 */
9
!(function($, window, document, navigator) {
10
    "use strict";
11
12
    /**
13
     * Vide settings
14
     * @private
15
     */
16
    var pluginName = "vide",
17
        defaults = {
18
            volume: 1,
19
            playbackRate: 1,
20
            muted: true,
21
            loop: true,
22
            autoplay: true,
23
            position: "50% 50%",
24
            posterType: "detect",
25
            resizing: true
26
        },
27
28
        // is iOs?
29
        isIOS = /iPad|iPhone|iPod/i.test(navigator.userAgent),
30
31
        // is Android?
32
        isAndroid = /Android/i.test(navigator.userAgent);
33
34
    /**
35
     * Parse string with options
36
     * @param {String} str
37
     * @returns {Object|String}
38
     * @private
39
     */
40
    function parseOptions(str) {
41
        var obj = {},
42
            delimiterIndex,
43
            option,
44
            prop, val,
45
            arr, len,
46
            i;
47
48
        // remove spaces around delimiters and split
49
        arr = str.replace(/\s*:\s*/g, ":").replace(/\s*,\s*/g, ",").split(",");
50
51
        // parse string
52
        for (i = 0, len = arr.length; i < len; i++) {
53
            option = arr[i];
54
55
            // Ignore urls and string without colon delimiters
56
            if (option.search(/^(http|https|ftp):\/\//) !== -1 ||
57
                option.search(":") === -1)
58
            {
59
                break;
60
            }
61
62
            delimiterIndex = option.indexOf(":");
63
            prop = option.substring(0, delimiterIndex);
64
            val = option.substring(delimiterIndex + 1);
65
66
            // if val is an empty string, make it undefined
67
            if (!val) {
68
                val = undefined;
69
            }
70
71
            // convert string value if it is like a boolean
72
            if (typeof val === "string") {
73
                val = val === "true" || (val === "false" ? false : val);
74
            }
75
76
            // convert string value if it is like a number
77
            if (typeof val === "string") {
78
                val = !isNaN(val) ? +val : val;
79
            }
80
81
            obj[prop] = val;
82
        }
83
84
        // if nothing is parsed
85
        if (prop == null && val == null) {
86
            return str;
87
        }
88
89
        return obj;
90
    }
91
92
    /**
93
     * Parse position option
94
     * @param {String} str
95
     * @returns {Object}
96
     * @private
97
     */
98
    function parsePosition(str) {
99
        str = "" + str;
100
101
        // default value is a center
102
        var args = str.split(/\s+/),
103
            x = "50%", y = "50%",
104
            len, arg,
105
            i;
106
107
        for (i = 0, len = args.length; i < len; i++) {
108
            arg = args[i];
109
110
            // convert values
111
            if (arg === "left") {
112
                x = "0%";
113
            } else if (arg === "right") {
114
                x = "100%";
115
            } else if (arg === "top") {
116
                y = "0%";
117
            } else if (arg === "bottom") {
118
                y = "100%";
119
            } else if (arg === "center") {
120
                if (i === 0) {
121
                    x = "50%";
122
                } else {
123
                    y = "50%";
124
                }
125
            } else {
126
                if (i === 0) {
127
                    x = arg;
128
                } else {
129
                    y = arg;
130
                }
131
            }
132
        }
133
134
        return { x: x, y: y };
135
    }
136
137
    /**
138
     * Search poster
139
     * @param {String} path
140
     * @param {Function} callback
141
     * @private
142
     */
143
    function findPoster(path, callback) {
144
        var onLoad = function() {
145
            callback(this.src);
146
        };
147
148
        $("<img src='" + path + ".gif'>").load(onLoad);
149
        $("<img src='" + path + ".jpg'>").load(onLoad);
150
        $("<img src='" + path + ".jpeg'>").load(onLoad);
151
        $("<img src='" + path + ".png'>").load(onLoad);
152
    }
153
154
    /**
155
     * Vide constructor
156
     * @param {HTMLElement} element
157
     * @param {Object|String} path
158
     * @param {Object|String} options
159
     * @constructor
160
     */
161
    function Vide(element, path, options) {
162
        this.$element = $(element);
163
164
        // parse path
165
        if (typeof path === "string") {
166
            path = parseOptions(path);
167
        }
168
169
        // parse options
170
        if (!options) {
171
            options = {};
172
        } else if (typeof options === "string") {
173
            options = parseOptions(options);
174
        }
175
176
        // remove extension
177
        if (typeof path === "string") {
178
            path = path.replace(/\.\w*$/, "");
179
        } else if (typeof path === "object") {
180
            for (var i in path) {
181
                if (path.hasOwnProperty(i)) {
182
                    path[i] = path[i].replace(/\.\w*$/, "");
183
                }
184
            }
185
        }
186
187
        this.settings = $.extend({}, defaults, options);
188
        this.path = path;
189
190
        this.init();
191
    }
192
193
    /**
194
     * Initialization
195
     * @public
196
     */
197
    Vide.prototype.init = function() {
198
        var vide = this,
199
            position = parsePosition(vide.settings.position),
200
            sources,
201
            poster;
202
203
        // Set video wrapper styles
204
        vide.$wrapper = $("<div>").css({
205
            "position": "absolute",
206
            "z-index": -1,
207
            "top": 0,
208
            "left": 0,
209
            "bottom": 0,
210
            "right": 0,
211
            "overflow": "hidden",
212
            "-webkit-background-size": "cover",
213
            "-moz-background-size": "cover",
214
            "-o-background-size": "cover",
215
            "background-size": "cover",
216
            "background-repeat": "no-repeat",
217
            "background-position": position.x + " " + position.y
218
        });
219
220
        // Get poster path
221
        poster = vide.path;
222
        if (typeof vide.path === "object") {
223
            if (vide.path.poster) {
224
                poster = vide.path.poster;
225
            } else {
226
                if (vide.path.mp4) {
227
                    poster = vide.path.mp4;
228
                } else if (vide.path.webm) {
229
                    poster = vide.path.webm;
230
                } else if (vide.path.ogv) {
231
                    poster = vide.path.ogv;
232
                }
233
            }
234
        }
235
236
        // Set video poster
237
//        if (vide.settings.posterType === "detect") {
238
//            findPoster(poster, function(url) {
239
//                vide.$wrapper.css("background-image", "url(" + url + ")");
240
//            });
241
//        } else if (vide.settings.posterType !== "none") {
242
//            vide.$wrapper
243
//                .css("background-image", "url(" + poster + "." + vide.settings.posterType + ")");
244
//        }
245
246
        // if parent element has a static position, make it relative
247
        if (vide.$element.css("position") === "static") {
248
            vide.$element.css("position", "relative");
249
        }
250
251
        vide.$element.prepend(vide.$wrapper);
252
253
        if (!isIOS && !isAndroid) {
254
            sources = "";
255
256
            if (typeof vide.path === "object") {
257
                if (vide.path.mp4) {
258
                    sources += "<source src='" + vide.path.mp4 + ".mp4' type='video/mp4'>";
259
                }
260
                if (vide.path.webm) {
261
                    sources += "<source src='" + vide.path.webm + ".webm' type='video/webm'>";
262
                }
263
                if (vide.path.ogv) {
264
                    sources += "<source src='" + vide.path.ogv + ".ogv' type='video/ogv'>";
265
                }
266
267
                vide.$video = $("<video>" + sources + "</video>");
268
            } else {
269
                vide.$video = $("<video>" +
270
                    "<source src='" + vide.path + ".mp4' type='video/mp4'>" +
271
                    "<source src='" + vide.path + ".webm' type='video/webm'>" +
272
                    "<source src='" + vide.path + ".ogv' type='video/ogg'>" +
273
                    "</video>");
274
            }
275
276
            // Disable visibility, while loading
277
            vide.$video.css("visibility", "hidden");
278
279
            // Set video properties
280
            vide.$video.prop({
281
                autoplay: vide.settings.autoplay,
282
                loop: vide.settings.loop,
283
                volume: vide.settings.volume,
284
                muted: vide.settings.muted,
285
                playbackRate: vide.settings.playbackRate
286
            });
287
288
            // Append video
289
            vide.$wrapper.append(vide.$video);
290
291
            // Video alignment
292
            vide.$video.css({
293
                "margin": "auto",
294
                "position": "absolute",
295
                "z-index": -1,
296
                "top": position.y,
297
                "left": position.x,
298
                "-webkit-transform": "translate(-" + position.x + ", -" + position.y + ")",
299
                "-ms-transform": "translate(-" + position.x + ", -" + position.y + ")",
300
                "transform": "translate(-" + position.x + ", -" + position.y + ")"
301
            });
302
303
            // resize video, when it's loaded
304
            vide.$video.bind("loadedmetadata." + pluginName, function() {
305
                vide.$video.css("visibility", "visible");
306
                vide.resize();
307
                vide.$wrapper.css("background-image", "none");
308
            });
309
310
            // resize event is available only for 'window',
311
            // use another code solutions to detect DOM elements resizing
312
            vide.$element.bind("resize." + pluginName, function() {
313
                if (vide.settings.resizing) {
314
                    vide.resize();
315
                }
316
            });
317
        }
318
    };
319
320
    /**
321
     * Get video element of the background
322
     * @returns {HTMLVideoElement|null}
323
     * @public
324
     */
325
    Vide.prototype.getVideoObject = function() {
326
        return this.$video ? this.$video[0] : null;
327
    };
328
329
    /**
330
     * Resize video background
331
     * @public
332
     */
333
    Vide.prototype.resize = function() {
334
        if (!this.$video) {
335
            return;
336
        }
337
338
        var
339
            // get native video size
340
            videoHeight = this.$video[0].videoHeight,
341
            videoWidth = this.$video[0].videoWidth,
342
343
            // get wrapper size
344
            wrapperHeight = this.$wrapper.height(),
345
            wrapperWidth = this.$wrapper.width();
346
347
        if (wrapperWidth / videoWidth > wrapperHeight / videoHeight) {
348
            this.$video.css({
349
                "width": wrapperWidth + 2,
350
351
                // +2 pixels to prevent empty space after transformation
352
                "height": "auto"
353
            });
354
        } else {
355
            this.$video.css({
356
                "width": "auto",
357
358
                // +2 pixels to prevent empty space after transformation
359
                "height": wrapperHeight + 2
360
            });
361
        }
362
    };
363
364
    /**
365
     * Destroy video background
366
     * @public
367
     */
368
    Vide.prototype.destroy = function() {
369
        this.$element.unbind(pluginName);
370
371
        if (this.$video) {
372
            this.$video.unbind(pluginName);
373
        }
374
375
        delete $[pluginName].lookup[this.index];
376
        this.$element.removeData(pluginName);
377
        this.$wrapper.remove();
378
    };
379
380
    /**
381
     * Special plugin object for instances.
382
     * @type {Object}
383
     * @public
384
     */
385
    $[pluginName] = {
386
        lookup: []
387
    };
388
389
    /**
390
     * Plugin constructor
391
     * @param {Object|String} path
392
     * @param {Object|String} options
393
     * @returns {JQuery}
394
     * @constructor
395
     */
396
    $.fn[pluginName] = function(path, options) {
397
        var instance;
398
399
        this.each(function() {
400
            instance = $.data(this, pluginName);
401
402
            if (instance) {
403
404
                // destroy plugin instance if exists
405
                instance.destroy();
406
            }
407
408
            // create plugin instance
409
            instance = new Vide(this, path, options);
410
            instance.index = $[pluginName].lookup.push(instance) - 1;
411
            $.data(this, pluginName, instance);
412
        });
413
414
        return this;
415
    };
416
417
    $(document).ready(function() {
418
419
        // window resize event listener
420
        $(window).bind("resize." + pluginName, function() {
421
            for (var len = $[pluginName].lookup.length, i = 0, instance; i < len; i++) {
422
                instance = $[pluginName].lookup[i];
423
424
                if (instance && instance.settings.resizing) {
425
                    instance.resize();
426
                }
427
            }
428
        });
429
430
        // Auto initialization.
431
        // Add 'data-vide-bg' attribute with a path to the video without extension.
432
        // Also you can pass options throw the 'data-vide-options' attribute.
433
        // 'data-vide-options' must be like "muted: false, volume: 0.5".
434
        $(document).find("[data-" + pluginName + "-bg]").each(function(i, element) {
435
            var $element = $(element),
436
                options = $element.data(pluginName + "-options"),
437
                path = $element.data(pluginName + "-bg");
438
439
            $element[pluginName](path, options);
440
        });
441
    });
442
})(window.jQuery, window, document, navigator);
443